package com.example.demo.yanglq.component.bloomfilter;

import com.example.demo.yanglq.component.jedis.RedisBitmaps;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.hash.Hashing;
import com.google.common.primitives.Longs;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @author by yanglq
 * @Classname RedisBloomFilter
 * @Description TODO
 * @Date 2020/1/7/007 15:04
 */
@Component
public class RedisBloomFilter {

    //预计插入量
    private long expectedInsertions;

    //可接受的错误率
    private double fpp;


    //hash函数数量
    private int numHashFunctions;

    @Autowired
    private RedisBitmaps redisBitmaps;


//    private RedisBloomFilter(long expectedInsertions, double fpp, int numHashFunctions) {
//        this.expectedInsertions = expectedInsertions;
//        this.fpp = fpp;
////        this.bitmaps = bitmaps;
//        this.numHashFunctions = numHashFunctions;
//    }

    public void create(Long expectedInsertions, double fpp) {
        Preconditions.checkArgument(expectedInsertions >= 0L, "Expected insertions (%s) must be >= 0", expectedInsertions);
        Preconditions.checkArgument(fpp > 0.0D, "False positive probability (%s) must be > 0.0", fpp);
        Preconditions.checkArgument(fpp < 1.0D, "False positive probability (%s) must be < 1.0", fpp);
//        Preconditions.checkNotNull(strategy);
        if (expectedInsertions == 0L) {
            expectedInsertions = 1L;
        }

        long numBits = optimalNumOfBits(expectedInsertions, fpp);
        int numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits);
        this.expectedInsertions = expectedInsertions;
        this.fpp = fpp;
//        this.bitmaps = bitmaps;
        this.numHashFunctions = numHashFunctions;
        redisBitmaps.setSize(numBits);
//        try {
//
//            return new RedisBloomFilter(expectedInsertions, fpp, numHashFunctions);
////            return new BloomFilter(new BloomFilterStrategies.LockFreeBitArray(numBits), numHashFunctions, funnel, strategy);
//        } catch (IllegalArgumentException var10) {
//            throw new IllegalArgumentException("Could not create BloomFilter of " + numBits + " bits", var10);
//        }

    }

    //计算hash函数个数
    private int optimalNumOfHashFunctions(long n, long m) {
        return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
    }

    //计算bit数组长度
    private long optimalNumOfBits(long n, double p) {
        if (p == 0) {
            p = Double.MIN_VALUE;
        }
        return (long) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
    }


    public void put(String string) {
        long bitSize = redisBitmaps.bitSize();
        byte[] bytes = Hashing.murmur3_128().hashString(string, Charsets.UTF_8).asBytes();
        long hash1 = lowerEight(bytes);
        long hash2 = upperEight(bytes);

        boolean bitsChanged = false;
        long combinedHash = hash1;
//        for (int i = 0; i < numHashFunctions; i++) {
//            bitsChanged |= bits.set((combinedHash & Long.MAX_VALUE) % bitSize);
//            combinedHash += hash2;
//        }
        long[] offsets = new long[numHashFunctions];
        for (int i = 0; i < numHashFunctions; i++) {
            offsets[i] = (combinedHash & Long.MAX_VALUE) % bitSize;
            combinedHash += hash2;
        }
        bitsChanged = redisBitmaps.set(offsets);
        redisBitmaps.ensureCapacityInternal();//自动扩容
//        return bitsChanged;
    }

    public boolean mightContain(String object) {
        long bitSize = redisBitmaps.bitSize();
        byte[] bytes = Hashing.murmur3_128().hashString(object, Charsets.UTF_8).asBytes();
        long hash1 = lowerEight(bytes);
        long hash2 = upperEight(bytes);
        long combinedHash = hash1;
//        for (int i = 0; i < numHashFunctions; i++) {
//            if (!redisBitmaps.get((combinedHash & Long.MAX_VALUE) % bitSize)) {
//                return false;
//            }
//            combinedHash += hash2;
//        }
//        return true;
        long[] offsets = new long[numHashFunctions];
        for (int i = 0; i < numHashFunctions; i++) {
            offsets[i] = (combinedHash & Long.MAX_VALUE) % bitSize;
            combinedHash += hash2;
        }
        return redisBitmaps.get(offsets);
    }

    private long lowerEight(byte[] bytes) {
        return Longs.fromBytes(bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]);
    }

    private long upperEight(byte[] bytes) {
        return Longs.fromBytes(bytes[15], bytes[14], bytes[13], bytes[12], bytes[11], bytes[10], bytes[9], bytes[8]);
    }

    public void resetBitmap() {
        redisBitmaps.reset();
    }

}
